﻿using System;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SunXNA.RainbowXNA.Batch;

namespace SunXNA.RainbowXNA.RenderObject
{
    class CTerrainPatchRenderObject : IBaseRenderObject
    {
        #region Public Types

        public struct VertexMultitextured
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
            public Vector4 TexWeights;

            public static int SizeInBytes = (3 + 3 + 4 + 4) * sizeof(float);
            public static VertexElement[] VertexElements = new VertexElement[]
            {
                new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
            };
        }


        #endregion

        #region Private Member Variables

        private VertexMultitextured[] m_vertices;
        private int[] m_indexes;

        private VertexDeclaration m_vertexDeclaration;
        private VertexBuffer m_vertexBuffer;
        private IndexBuffer m_indexBuffer;

        private BoundingSphere m_graphicsVolume;
        private BoundingBox m_boundingBox;

        private int m_totalTriangles;

        #endregion

        #region Protected Member Variables
        #endregion

        #region Puplic Member Properties

        public BoundingSphere Volume
        {
            get
            {
                return m_graphicsVolume;
            }
        }

        public BoundingBox Box
        {
            get
            {
                return m_boundingBox;
            }
        }

        #endregion

        #region Public Member Functions

        public CTerrainPatchRenderObject(GraphicsDevice device, CHeightMap heightMap, int offsetX, int offsetZ, int vertsWide, int vertsDeep, float cellWidth, float cellDepth)
        {
            Trace.Assert(heightMap.Width * heightMap.Depth < (uint)(int.MaxValue));

            SetUpVertexBuffer(device, heightMap, cellWidth, cellDepth, offsetX, offsetZ, vertsWide, vertsDeep);
            SetUpIndexBuffer(device, vertsWide, vertsDeep);
            SetBuffers(device);
        }

        public void Draw(GraphicsDevice device, Matrix m4World, Matrix m4View, Matrix m4Projection)
        {
            device.RenderState.DepthBufferEnable = true;
            device.RenderState.DepthBufferWriteEnable = true;

            //device.RenderState.FillMode = FillMode.WireFrame;

            device.VertexDeclaration = m_vertexDeclaration;

            device.Vertices[0].SetSource(m_vertexBuffer, 0, VertexMultitextured.SizeInBytes);
            device.Indices = m_indexBuffer;

            device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, m_vertices.Length, 0, m_totalTriangles);
            //device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, m_vertices, 0, m_vertices.Length);
        }

        public void AddToBatch(CBatchManager batchManager, CRainbowXNA.CRenderInstance instance)
        {
            batchManager.Add(instance);
        }

        public CRainbowXNA.CRenderInstance CreateDefaultInstance(CRainbowXNA.RenderObjectHandleType handle, CRainbowXNA rainbow)
        {
            Trace.Assert(handle.RenderObject == this);
            //return rainbow.CreateRenderInstance(handle);
            return new CRainbowXNA.CRenderInstance(handle);
        }

        #endregion

        #region Protected Member Functions
        #endregion

        #region Private Member Functions

        private void SetUpVertexBuffer(GraphicsDevice device, CHeightMap heightMap, float cellWidth, float cellDepth, int offsetX, int offsetZ, int vertsWide, int vertsDeep)
        {
            m_vertices = new VertexMultitextured[vertsWide * vertsDeep];

            //Vector3 topRight = new Vector3(vertsWide * cellWidth * 0.5f,
            //                               0.0f,
            //                               vertsDeep * cellDepth * 0.5f);

            //Vector3 topRight = (((float)vertsWide * cellWidth * 0.5f) * Vector3.Left) + (((float)vertsDeep * cellDepth * 0.5f) * Vector3.Forward);

            Vector3 topRight = new Vector3(0.0f, 0.0f, 0.0f);

            float rMinX = float.MaxValue;
            float rMaxX = float.MinValue;

            float rMinY = float.MaxValue;
            float rMaxY = float.MinValue;

            float rMinZ = float.MaxValue;
            float rMaxZ = float.MinValue;

            //int vertsWide = heightMap.Width;
            //int vertsDeep = heightMap.Depth;

            int lastVertX = offsetX + vertsWide;
            int lastVertZ = offsetZ + vertsDeep;

            int currentIndex = 0;

            for (int z = offsetZ; z < lastVertZ; ++z)
            {
                for (int x = offsetX; x < lastVertX; ++x)
                {
                    int currentVertexIndex = x + z * vertsWide;

                    m_vertices[currentIndex].Position.X = topRight.X + ((float)(x-offsetX) * cellWidth);
                    m_vertices[currentIndex].Position.Y = heightMap.GetHeightValue(x, z);
                    m_vertices[currentIndex].Position.Z = topRight.Z + ((float)(z - offsetZ) * cellDepth);
                    m_vertices[currentIndex].TextureCoordinate.X = ((float)x) / ((float)heightMap.Width);
                    m_vertices[currentIndex].TextureCoordinate.Y = ((float)z) / ((float)heightMap.Depth);

                    float heightPercentage = heightMap.GetHeightPercentage(x, z);

                    float xScale = (heightPercentage < 0.1f ) ? 1.0f : 0.0f;
                    float yScale = (heightPercentage >= 0.1f && heightPercentage < 0.2f) ? 1.0f : 0.0f;
                    float zScale = (heightPercentage >= 0.2f && heightPercentage < 0.6f) ? 1.0f : 0.0f;
                    float wScale = (heightPercentage >= 0.6f) ? 1.0f : 0.0f;

                    m_vertices[currentIndex].TexWeights.X = xScale;
                    m_vertices[currentIndex].TexWeights.Y = yScale;
                    m_vertices[currentIndex].TexWeights.Z = zScale;
                    m_vertices[currentIndex].TexWeights.W = wScale;

                    m_vertices[currentIndex].Normal = heightMap.GetNormal(x, z, cellWidth, cellDepth);

                    if (m_vertices[currentIndex].Position.X < rMinX)
                    {
                        rMinX = m_vertices[currentIndex].Position.X;
                    }

                    if (m_vertices[currentIndex].Position.X > rMaxX)
                    {
                        rMaxX = m_vertices[currentIndex].Position.X;
                    }

                    if (m_vertices[currentIndex].Position.Y < rMinY)
                    {
                        rMinY = m_vertices[currentIndex].Position.Y;
                    }

                    if (m_vertices[currentIndex].Position.Y > rMaxY)
                    {
                        rMaxY = m_vertices[currentIndex].Position.Y;
                    }

                    if (m_vertices[currentIndex].Position.Z < rMinZ)
                    {
                        rMinZ = m_vertices[currentIndex].Position.Z;
                    }

                    if (m_vertices[currentIndex].Position.Z > rMaxZ)
                    {
                        rMaxZ = m_vertices[currentIndex].Position.Z;
                    }

                    ++currentIndex;
                }
            }

            m_boundingBox.Min.X = rMinX;
            m_boundingBox.Min.Y = rMinY;
            m_boundingBox.Min.Z = rMinZ;

            m_boundingBox.Max.X = rMaxX;
            m_boundingBox.Max.Y = rMaxY;
            m_boundingBox.Max.Z = rMaxZ;

            m_graphicsVolume = BoundingSphere.CreateFromBoundingBox(m_boundingBox);
        }

        private void SetUpIndexBuffer(GraphicsDevice device, int vertsWide, int vertsDeep)
        {
            int cellsWide = vertsWide - 1;
            int cellsDeep = vertsDeep - 1;

            int totalCells = cellsWide * cellsDeep;
            int trianglesPerCell = 2;
            int indecesPerTriangle = 3;
            m_totalTriangles = (int)(trianglesPerCell * totalCells);

            int totalIndeces = (int)(m_totalTriangles * indecesPerTriangle);

            m_indexes = new int[totalIndeces];

            uint count = 0;
            for (int z = 0; z < cellsDeep; ++z)
            {
                for (int x = 0; x < cellsWide; ++x)
                {
                    //
                    //  Bottom Left triangle
                    //
                    m_indexes[count++] = x + z * vertsWide;
                    m_indexes[count++] = (x + 1) + z * vertsWide;
                    m_indexes[count++] = x + (z + 1) * vertsWide;

                    //
                    //  Top right triangle
                    //
                    m_indexes[count++] = x + (z + 1) * vertsWide;
                    m_indexes[count++] = (x + 1) + z * vertsWide;
                    m_indexes[count++] = (x + 1) + (z + 1) * vertsWide;
                }
            }

            Trace.Assert(count == m_indexes.Length);
        }

        private void SetBuffers(GraphicsDevice device)
        {
            m_vertexDeclaration = new VertexDeclaration(device, VertexMultitextured.VertexElements);
            m_vertexBuffer = new VertexBuffer(device, VertexMultitextured.SizeInBytes * m_vertices.Length, BufferUsage.WriteOnly);
            m_vertexBuffer.SetData<VertexMultitextured>(m_vertices);

            m_indexBuffer = new IndexBuffer(device, sizeof(int) * m_indexes.Length, BufferUsage.WriteOnly, IndexElementSize.ThirtyTwoBits);
            m_indexBuffer.SetData<int>(m_indexes);
        }

        #endregion
    }
}
